﻿// ===============================================================================
//  Copyright © ymind.net. All rights reserved .
//  官方网站：http://ymind.net/
//  版权所有：彦铭工作室 老陈出品
// ===============================================================================

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Lcsoft.TemplateEngine.Tags;
using Lcsoft.TemplateEngine.XHtmlParser;

namespace Lcsoft.TemplateEngine.Core
{
	/// <summary>
	/// 表示能够将 .xhtml 文档转换为 .aspx 文档的模板引擎。
	/// </summary>
	[ComVisible(true)]
	[Serializable]
	public sealed class AspxTemplateEngine
	{
		private readonly bool _processSSI;

		/// <summary>
		/// 获取模板引擎能够处理的语句标签的类型的集合。
		/// </summary>
		private readonly SafeDictionary<string, Type> _templateTags = new SafeDictionary<string, Type>(StringComparer.CurrentCultureIgnoreCase);

		/// <summary>
		/// 初始化 <see cref="AspxTemplateEngine"/> 类的新实例。
		/// </summary>
		/// <param name="fileName">指定模板文件的完整物理路径。</param>
		/// <param name="applicationPath">指定应用程序根路径。</param>
		public AspxTemplateEngine(string fileName, string applicationPath) : this(fileName, applicationPath, Encoding.UTF8) { }

		/// <summary>
		/// 初始化 <see cref="AspxTemplateEngine"/> 类的新实例。
		/// </summary>
		/// <param name="fileName">指定模板文件的完整物理路径。</param>
		/// <param name="applicationPath">指定应用程序根路径。</param>
		/// <param name="encoding">指定模板文件的字符编码。</param>
		public AspxTemplateEngine(string fileName, string applicationPath, Encoding encoding) : this(fileName, applicationPath, encoding, "lc") { }

		/// <summary>
		/// 初始化 <see cref="AspxTemplateEngine"/> 类的新实例。
		/// </summary>
		/// <param name="fileName">指定模板文件的完整物理路径。</param>
		/// <param name="applicationPath">指定应用程序根路径。</param>
		/// <param name="encoding">指定模板文件的字符编码。</param>
		/// <param name="templateElementPrefix">指定模板语句标签的前缀名。</param>
		public AspxTemplateEngine(string fileName, string applicationPath, Encoding encoding, string templateElementPrefix) : this(fileName, applicationPath, encoding, templateElementPrefix, true) { }

		/// <summary>
		/// 初始化 <see cref="AspxTemplateEngine"/> 类的新实例。
		/// </summary>
		/// <param name="fileName">指定模板文件的完整物理路径。</param>
		/// <param name="applicationPath">指定应用程序根路径。</param>
		/// <param name="encoding">指定模板文件的字符编码。</param>
		/// <param name="templateElementPrefix">指定模板语句标签的前缀名。</param>
		/// <param name="processSSI">指定是否处理 SSI 嵌套文档。</param>
		public AspxTemplateEngine(string fileName, string applicationPath, Encoding encoding, string templateElementPrefix, bool processSSI) : this(fileName, applicationPath, encoding, templateElementPrefix, processSSI, 20) { }

		/// <summary>
		/// 初始化 <see cref="AspxTemplateEngine"/> 类的新实例。
		/// </summary>
		/// <param name="fileName">指定模板文件的完整物理路径。</param>
		/// <param name="applicationPath">指定应用程序根路径。</param>
		/// <param name="encoding">指定模板文件的字符编码。</param>
		/// <param name="templateElementPrefix">指定模板语句标签的前缀名。</param>
		/// <param name="processSSI">指定是否处理 SSI 嵌套文档。</param>
		/// <param name="maxNestting">指定处理 SSI 嵌套文档允许的最大嵌套层数。</param>
		public AspxTemplateEngine(string fileName, string applicationPath, Encoding encoding, string templateElementPrefix, bool processSSI, int maxNestting) : this(fileName, applicationPath, encoding, templateElementPrefix, processSSI, maxNestting, ApplicationInfo.YMind) { }

		/// <summary>
		/// 初始化 <see cref="AspxTemplateEngine"/> 类的新实例。
		/// </summary>
		/// <param name="fileName">指定模板文件的完整物理路径。</param>
		/// <param name="applicationPath">指定应用程序根路径。</param>
		/// <param name="encoding">指定模板文件的字符编码。</param>
		/// <param name="templateElementPrefix">指定模板语句标签的前缀名。</param>
		/// <param name="processSSI">指定是否处理 SSI 嵌套文档。</param>
		/// <param name="application">指定应用程序信息，用于自定义版权信息。</param>
		public AspxTemplateEngine(string fileName, string applicationPath, Encoding encoding, string templateElementPrefix, bool processSSI, ApplicationInfo application) : this(fileName, applicationPath, encoding, templateElementPrefix, processSSI, 20, application) { }

		/// <summary>
		/// 初始化 <see cref="AspxTemplateEngine"/> 类的新实例。
		/// </summary>
		/// <param name="fileName">指定模板文件的完整物理路径。</param>
		/// <param name="applicationPath">指定应用程序根路径。</param>
		/// <param name="encoding">指定模板文件的字符编码。</param>
		/// <param name="templateElementPrefix">指定模板语句标签的前缀名。</param>
		/// <param name="processSSI">指定是否处理 SSI 嵌套文档。</param>
		/// <param name="maxNestting">指定处理 SSI 嵌套文档允许的最大嵌套层数。</param>
		/// <param name="application">指定应用程序信息，用于自定义版权信息。</param>
		public AspxTemplateEngine(string fileName, string applicationPath, Encoding encoding, string templateElementPrefix, bool processSSI, int maxNestting, ApplicationInfo application)
		{
			if (fileName == null) throw new ArgumentNullException("fileName");
			if (fileName.Length == 0) throw new ArgumentOutOfRangeException("fileName");
			if (Path.IsPathRooted(fileName) == false) throw new ArgumentException("参数 fileName 必须是绝对路径。", "fileName");
			if (File.Exists(fileName) == false) throw new FileNotFoundException(String.Format(@"无法找到指定文件：""{0}""。", fileName));

			if (applicationPath == null) throw new ArgumentNullException("applicationPath");
			if (applicationPath.Length == 0) throw new ArgumentOutOfRangeException("applicationPath");
			if (Path.IsPathRooted(applicationPath) == false) throw new ArgumentException("参数 applicationPath 必须是绝对路径。", "applicationPath");
			if (Directory.Exists(applicationPath) == false) throw new DirectoryNotFoundException(String.Format(@"无法找到指定路径：""{0}""。", applicationPath));

			if (templateElementPrefix == null) throw new ArgumentNullException("templateElementPrefix");
			if (templateElementPrefix.Length == 0) throw new ArgumentOutOfRangeException("templateElementPrefix");

			if (maxNestting <= 0) throw new ArgumentOutOfRangeException("maxNestting", "参数 maxNestting 的值必须大于 0。");

			this.FileName = fileName;
			this.ApplicationPath = String.Format("{0}\\", applicationPath.TrimEnd('/', '\\'));
			this.DocumentEncoding = encoding;
			this.TemplateElementPrefix = templateElementPrefix;
			this._processSSI = processSSI;
			this.MaxNestting = maxNestting;
			this.Application = application == ApplicationInfo.Empty ? ApplicationInfo.YMind : application;
			this.EnableRuntimeHandler = false;
			this.EnableSDE = false;
			this.EnableStopwatch = true;
			this.EnableFilePacking = false;
			this.TemplateFiles = new List<string> {
				this.FileName
			};
			this.StaticVariables = new SafeDictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);

			this._InitializeTemplateTags();
			this._InitStaticVariables();
		}

		/// <summary>
		/// 获取模板引擎能够处理的语句标签的类型的集合。
		/// </summary>
		public List<KeyValuePair<string, Type>> TemplateTags
		{
			get { return this._templateTags.ToList(); }
		}

		/// <summary>
		/// 获取引用模板引擎提供程序的应用程序的信息。
		/// </summary>
		public ApplicationInfo Application { get; private set; }

		/// <summary>
		/// 获取模板文件的完整物理路径。
		/// </summary>
		public string FileName { get; private set; }

		/// <summary>
		/// 获取初始化模板引擎时使用的应用程序根路径。
		/// </summary>
		public string ApplicationPath { get; private set; }

		/// <summary>
		/// 获取模板语句标签的前缀名。
		/// </summary>
		public string TemplateElementPrefix { get; private set; }

		/// <summary>
		/// 获取当前模板引擎正在处理的 xhtml 文档的实例。
		/// </summary>
		public XHtmlDocument Document { get; private set; }

		/// <summary>
		/// 获取模板引擎提供程序正在处理的模板代码的字符编码。
		/// </summary>
		public Encoding DocumentEncoding { get; private set; }

		/// <summary>
		/// 获取开始处理模板时的时间。
		/// </summary>
		public DateTime ProcessTime { get; private set; }

		/// <summary>
		/// 获取处理 SSI 文档时允许嵌套的最大层数。
		/// </summary>
		public int MaxNestting { get; private set; }

		/// <summary>
		/// 获取模板引擎提供程序正在处理的模板代码所关联的所有文件的列表，该列表包含的是文件完整的物理路径。
		/// </summary>
		public List<string> TemplateFiles { get; private set; }

		/// <summary>
		/// 获取模板引擎提供程序正在使用的静态变量字典对象的实例。
		/// </summary>
		public SafeDictionary<string, string> StaticVariables { get; set; }

		/// <summary>
		/// 获取或设置模板引擎提供程序正在使用的多语言引擎的实例。仅在处理多语言模板的时候才需要。
		/// </summary>
		public MultiLanguageEngineBase LanguageEngine { get; set; }

		/// <summary>
		/// 获取或设置一个值，该值指示生成结果的代码当中是否包含动静平衡功能（Static And Dynamic Equilibrium）。如果设置为 true，您必须在相关的类中实现 <see cref="ISDEHandler"/> 接口。
		/// </summary>
		public bool EnableSDE { get; set; }

		/// <summary>
		/// 获取或设置一个值，该值指示生成结果的代码当中是否包含使用 <see cref="System.Diagnostics.Stopwatch"/> 测量运行时间的功能。如果设置为 true，会自动为页面添加 <see cref="System.Diagnostics"/> 命名空间，在模板代码中调用 {$this.Runningwatch$} 即可访问由此创建的 <see cref="System.Diagnostics.Stopwatch"/> 的实例。
		/// </summary>
		public bool EnableStopwatch { get; set; }

		/// <summary>
		/// 获取或设置一个值，该值指示生成结果的代码当中是否包含自定义运行时支持的功能。如果设置为 true，您必须在相关的类中实现 <see cref="IRuntimeHandler"/> 接口。
		/// </summary>
		public bool EnableRuntimeHandler { get; set; }

		/// <summary>
		/// 获取或设置一个值，该值指示是否开启 .css 和 .js 文档打包功能。
		/// </summary>
		public bool EnableFilePacking { get; set; }

		/// <summary>
		/// 获取模板引擎提供程序的产品名称。
		/// </summary>
		public string ProductName
		{
			get { return "老陈Asp.Net模板引擎提供程序"; }
		}

		/// <summary>
		/// 获取模板引擎提供程序的友好名称，用于在配置过程中引用提供程序。
		/// </summary>
		public string Name
		{
			get { return "TemplateEngineProvider"; }
		}

		/// <summary>
		/// 获取模板引擎提供程序的简短易懂的描述，它适合在管理工具或其他用户界面 (UI) 中显示。
		/// </summary>
		public string Description
		{
			get { return "提供了针对老陈Asp.Net模板引擎模型的基实现。"; }
		}

		/// <summary>
		/// 获取模板引擎提供程序的作者。
		/// </summary>
		public string Author
		{
			get { return "YMind Chan"; }
		}

		/// <summary>
		/// 获取模板引擎提供程序的版本信息。
		/// </summary>
		public Version Version
		{
			get { return TemplateEngineHelper.Version; }
		}

		/// <summary>
		/// 获取模板引擎提供程序的发布日期。
		/// </summary>
		public DateTime ProductDate
		{
			get { return TemplateEngineHelper.ProductDate; }
		}

		/// <summary>
		/// 获取模板引擎提供程序的主页链接地址。
		/// </summary>
		public string HomePage
		{
			get { return "http://www.ymind.net/"; }
		}

		/// <summary>
		/// 获取模板引擎提供程序的技术支持链接地址。
		/// </summary>
		public string SupportPage
		{
			get { return "http://www.ymind.net/"; }
		}

		/// <summary>
		/// 获取模板引擎提供程序的版权所有者。
		/// </summary>
		public string CopyrightHolder
		{
			get { return "彦铭工作室"; }
		}

		/// <summary>
		/// 在保存合并后的 .css 文件时发生，订阅此事件可以自定义要保存的文件的路径。
		/// </summary>
		public event CreateFileNameEventHandler CreateStyleSheetFile;

		/// <summary>
		/// 在保存合并后的 .js 文件时发生，订阅此事件可以自定义要保存的文件的路径。
		/// </summary>
		public event CreateFileNameEventHandler CreateJavascriptFile;

		/// <summary>
		/// 初始化预置模板引擎语句标签。
		/// </summary>
		private void _InitializeTemplateTags()
		{
			this._templateTags.Add("AddStaticvar", typeof(AddStaticvarTag), true);
			this._templateTags.Add("break", typeof(BreakTag), true);
			this._templateTags.Add("continue", typeof(ContinueTag), true);
			this._templateTags.Add("elseif", typeof(ElseIfTag), true);
			this._templateTags.Add("else", typeof(ElseTag), true);
			this._templateTags.Add("feature", typeof(FeatureTag), true);
			this._templateTags.Add("foreach", typeof(ForeachTag), true);
			this._templateTags.Add("for", typeof(ForTag), true);
			this._templateTags.Add("if", typeof(IfTag), true);
			this._templateTags.Add("important", typeof(ImportantTag), true);
			this._templateTags.Add("import", typeof(ImportTag), true);
			this._templateTags.Add("inherits", typeof(InheritsTag), true);
			this._templateTags.Add("packing", typeof(PackingTag), true);
			this._templateTags.Add("return", typeof(ReturnTag), true);
			this._templateTags.Add("using", typeof(UsingTag), true);
			this._templateTags.Add("var", typeof(VarTag), true);
			this._templateTags.Add("while", typeof(WhileTag), true);
		}

		/// <summary>
		/// 初始化预置静态变量的功能。
		/// </summary>
		private void _InitStaticVariables()
		{
			this.StaticVariables.Add("TemplateEngine", this.ToString(), true);
			this.StaticVariables.Add("TemplateEngine.ProductName", this.ProductName, true);
			this.StaticVariables.Add("TemplateEngine.Name", this.Name, true);
			this.StaticVariables.Add("TemplateEngine.Description", this.Description, true);
			this.StaticVariables.Add("TemplateEngine.Author", this.Author, true);
			this.StaticVariables.Add("TemplateEngine.Version", this.Version.ToString(), true);
			this.StaticVariables.Add("TemplateEngine.ProductDate", this.ProductDate.ToString("yyyy-MM-dd HH:mm:ss"), true);
			this.StaticVariables.Add("TemplateEngine.HomePage", this.HomePage, true);
			this.StaticVariables.Add("TemplateEngine.SupportPage", this.SupportPage, true);
			this.StaticVariables.Add("TemplateEngine.CopyrightHolder", this.CopyrightHolder, true);

			this.StaticVariables.Add("Application", this.Application.ToString(), true);
			this.StaticVariables.Add("Application.Author", this.Application.Author, true);
			this.StaticVariables.Add("Application.CopyrightHolder", this.Application.CopyrightHolder, true);
			this.StaticVariables.Add("Application.Guid", this.Application.Guid.ToString(), true);
			this.StaticVariables.Add("Application.HomePage", this.Application.HomePage, true);
			this.StaticVariables.Add("Application.ProductDate", this.Application.ProductDate.ToString("yyyy-MM-dd HH:mm:ss"), true);
			this.StaticVariables.Add("Application.Name", this.Application.Name, true);
			this.StaticVariables.Add("Application.ProductName", this.Application.ProductName, true);
			this.StaticVariables.Add("Application.Version", this.Application.Version.ToString(), true);
			this.StaticVariables.Add("Application.SupportPage", this.Application.SupportPage, true);
		}

		/// <summary>
		/// 处理保存合并后的 .css 文件的事件。
		/// </summary>
		/// <param name="sender">事件源。</param>
		/// <param name="e">包含事件数据的 <see cref="CreateFileNameEventArgs"/>。</param>
		private void _OnCreateStyleSheetFile(object sender, CreateFileNameEventArgs e)
		{
			if (this.CreateStyleSheetFile == null) return;

			this.CreateStyleSheetFile(sender, e);
		}

		/// <summary>
		/// 处理保存合并后的 .js 文件的事件。
		/// </summary>
		/// <param name="sender">事件源。</param>
		/// <param name="e">包含事件数据的 <see cref="CreateFileNameEventArgs"/>。</param>
		private void _OnCreateJavascriptFile(object sender, CreateFileNameEventArgs e)
		{
			if (this.CreateJavascriptFile == null) return;

			this.CreateJavascriptFile(sender, e);
		}

		private StringBuilder _ProcessStaticVariable(string document)
		{
			if (String.IsNullOrEmpty(document)) return new StringBuilder();

			var tags = this.Document.Nodes.FindAllNodes<AddStaticvarTag>(item => item.Disabled == false);

			tags.ForEach(tag => this.StaticVariables.Add(tag.Key, tag.Value));

			var result = new StringBuilder(document);

			if (document.IndexOf("{#", StringComparison.Ordinal) >= 0)
			{
				var tokens = new List<string>();

				var indexStart = document.IndexOf("{#", StringComparison.Ordinal);

				while (indexStart < document.Length && indexStart >= 0)
				{
					var indexEnd = document.IndexOf("#}", indexStart + 2, StringComparison.Ordinal);

					if (indexEnd == -1) break;

					var value = document.Substring(indexStart, indexEnd - indexStart + 2);

					if (tokens.Contains(value) == false) tokens.Add(value);

					indexStart = document.IndexOf("{#", indexEnd + 2, StringComparison.Ordinal);
				}

				foreach (var token in tokens)
				{
					var key = token.Trim('{', '}', '#');
					var value = this.StaticVariables.ContainsKey(key) ? this.StaticVariables[key] : token;

					result.Replace(token, value);
				}
			}

			return result;
		}

		private StringBuilder _ProcessPackingTags(string document)
		{
			if (String.IsNullOrEmpty(document)) return new StringBuilder();
			if (this.CreateStyleSheetFile == null && this.CreateJavascriptFile == null) return new StringBuilder(document);
			if (this.Document.HeadElement == null) return new StringBuilder(document);

			var rootPath = Path.GetDirectoryName(this.ApplicationPath);

			if (rootPath == null) throw new ParseException("文件打包", "指定的文件夹路径为空或不存在！");

			// css
			var styletags = this.Document.Nodes.FindAllNodes<PackingTag>(item => item.Type == PackingKind.StyleSheet && item.Disabled == false);
			var styleFiles = new Dictionary<FileInfo, PackingTag>();

			styletags.ForEach(item =>
			{
				if (String.IsNullOrEmpty(item.FileName)) return;

				var file = new FileInfo(Path.Combine(rootPath, item.FileName.Replace('/', '\\').TrimStart('\\')));

				if (file.Exists && styleFiles.ContainsKey(file) == false) styleFiles.Add(file, item);
			});

			if (styleFiles.Count > 0 && this.CreateStyleSheetFile != null)
			{
				var linkElements = this.Document.Nodes.FindAllNodes<XHtmlLinkElement>();

				if (linkElements.Count > 0)
				{
					var styles = new Dictionary<FileInfo, PackingTag>();

					foreach (var pair in styleFiles)
					{
						foreach (var linkElement in linkElements)
						{
							if (String.Compare(linkElement.Href, pair.Value.Match, StringComparison.OrdinalIgnoreCase) != 0) continue;

							styles.Add(pair.Key, pair.Value);

							var element = linkElement;

							this.Document.Nodes.RemoveAllNodes<XHtmlLinkElement>(item => item.Href == element.Href);
						}
					}

					var args = new CreateFileNameEventArgs(this, styles);

					this._OnCreateStyleSheetFile(this, args);

					if (String.IsNullOrEmpty(args.FileName) == false)
					{
						var element = new XHtmlLinkElement(1, 1);
						element.Attributes.Add("href", new XHtmlAttribute("href", args.FileName));
						element.Attributes.Add("type", new XHtmlAttribute("type", "text/css"));
						element.Attributes.Add("rel", new XHtmlAttribute("rel", "stylesheet"));

						this.Document.HeadElement.Nodes.Add(element);
						this.Document.HeadElement.Nodes.Add(new XHtmlText(Environment.NewLine, 1, 1));
					}
				}
			}

			// js
			var scripttags = this.Document.Nodes.FindAllNodes<PackingTag>(item => item.Type == PackingKind.Javascript && item.Disabled == false);
			var scriptFiles = new Dictionary<FileInfo, PackingTag>();

			scripttags.ForEach(item =>
			{
				if (String.IsNullOrEmpty(item.FileName)) return;

				var file = new FileInfo(Path.Combine(rootPath, item.FileName.Replace('/', '\\').TrimStart('\\')));

				if (file.Exists && scriptFiles.ContainsKey(file) == false) scriptFiles.Add(file, item);
			});

			if (scriptFiles.Count > 0 && this.CreateJavascriptFile != null)
			{
				var scriptElements = this.Document.Nodes.FindAllNodes<XHtmlScriptElement>();

				if (scriptElements.Count > 0)
				{
					var scripts = new Dictionary<FileInfo, PackingTag>();

					foreach (var pair in scriptFiles)
					{
						foreach (var scriptElement in scriptElements)
						{
							if (String.Compare(scriptElement.Src, pair.Value.Match, StringComparison.OrdinalIgnoreCase) != 0) continue;

							scripts.Add(pair.Key, pair.Value);

							var element = scriptElement;

							this.Document.Nodes.RemoveAllNodes<XHtmlScriptElement>(item => item.Src == element.Src);
						}
					}

					var args = new CreateFileNameEventArgs(this, scripts);

					this._OnCreateJavascriptFile(this, args);

					if (String.IsNullOrEmpty(args.FileName) == false)
					{
						var element = new XHtmlScriptElement(1, 1) {
							IsExplicitlyTerminated = true
						};
						element.Attributes.Add("src", new XHtmlAttribute("src", args.FileName));
						element.Attributes.Add("type", new XHtmlAttribute("type", "text/javascript"));

						this.Document.HeadElement.Nodes.Add(element);
						this.Document.HeadElement.Nodes.Add(new XHtmlText(Environment.NewLine, 1, 1));
					}
				}
			}

			return new StringBuilder(this.Document.ToHtml());
		}

		private StringBuilder _ProcessLanguageLabel(string document)
		{
			if (String.IsNullOrEmpty(document)) return new StringBuilder();

			//<div>{$Lang[LangItem1]$}</div>
			//<div>{$Lang[LangItem1, zh-CHT]$}</div>
			var result = new StringBuilder(document);

			if (this.LanguageEngine == null || !this.LanguageEngine.LanguagePacks.Any()) return result;

			if (document.IndexOf("{$Lang[", StringComparison.Ordinal) >= 0)
			{
				var tokens = new List<string>();

				var indexStart = document.IndexOf("{$Lang[", StringComparison.Ordinal);

				while (indexStart < document.Length && indexStart >= 0)
				{
					var indexEnd = document.IndexOf("]$}", indexStart + 7, StringComparison.Ordinal);

					if (indexEnd == -1) break;

					var value = document.Substring(indexStart, indexEnd - indexStart + 3);

					if (tokens.Contains(value) == false) tokens.Add(value);

					indexStart = document.IndexOf("{$Lang[", indexEnd + 3, StringComparison.Ordinal);
				}

				foreach (var token in tokens)
				{
					var label = token.Remove(token.Length - 3).Remove(0, 7);
					string langName;
					var langCode = String.Empty;
					var commaIndex = label.IndexOf(',');

					if (commaIndex == -1) langName = label;
					else
					{
						langName = label.Substring(0, commaIndex).Trim();
						langCode = label.Substring(commaIndex + 1).Trim();
					}

					if (String.IsNullOrEmpty(langName)) throw new ParseException("语法错误", "未指定语言项的名称。");

					var flag = this.LanguageEngine.LanguagePack.Items.Any(item => String.Compare(item.Name, langName, StringComparison.OrdinalIgnoreCase) == 0);
					var langValue = String.IsNullOrEmpty(langCode) ? (flag ? this.LanguageEngine[langName] : token) : (flag ? this.LanguageEngine.ReadValue(langName, langCode) : token);

					result.Replace(token, langValue);
				}
			}

			return result;
		}

		private void _ProcessFeatures()
		{
			var featureTags = this.Document.Nodes.FindAllNodes<FeatureTag>(item => item.Disabled == false).ToList();

			if (featureTags.Count == 0) return;

			var tag = featureTags.Last();

			this.EnableRuntimeHandler = tag.EnableRuntimeHandler;
			this.EnableSDE = tag.EnableSDE;
			this.EnableStopwatch = tag.EnableStopwatch;
			this.EnableFilePacking = tag.EnableFilePacking;
		}

		/// <summary>
		/// 返回包含 aspx 文档的 <see cref="System.Text.StringBuilder"/>。
		/// </summary>
		/// <returns>返回 <see cref="System.Text.StringBuilder"/>。</returns>
		public StringBuilder ToAspx()
		{
			this.ProcessTime = DateTime.Now;

			using (var reader = File.OpenText(this.FileName))
			{
				this.Document = new XHtmlDocument(this.TemplateElementPrefix, Path.GetDirectoryName(this.FileName), reader, this._processSSI, this.MaxNestting, this._templateTags);

				this.TemplateFiles.AddRange(this.Document.Files);
				this.TemplateFiles.Sort();

				// 处理功能开关
				this._ProcessFeatures();
			}

			var document = new StringBuilder(this.Document.ToHtml());

			// 处理 css/js 代码合并
			if (this.EnableFilePacking) document = this._ProcessPackingTags(document.ToString());

			// 处理多语言支持
			document = this._ProcessLanguageLabel(document.ToString());

			// 处理静态变量标记
			document = this._ProcessStaticVariable(document.ToString());

			// 对模板代码执行二次解析
			this.Document = new XHtmlDocument(this.TemplateElementPrefix, this.ApplicationPath, new StringReader(document.ToString()), this._processSSI, this.MaxNestting, this._templateTags);

			// 取得命名空间
			var namespaces = this.Document.Nodes.FindAllNodes<ImportTag>(item => item.Disabled == false).Select(item => item.Namespace).Where(item => String.IsNullOrEmpty(item) == false).ToList();

			if (this.EnableStopwatch) namespaces.Add("System.Diagnostics");
			if (this.EnableRuntimeHandler || this.EnableSDE || this.LanguageEngine != null) namespaces.Add("Lcsoft.TemplateEngine");

			namespaces.Sort();

			// 取得类名，如果出现了多个则取最后一个
			var inherits = this.Document.Nodes.FindAllNodes<InheritsTag>(item => item.Disabled == false);

			var aspx = new StringBuilder();

			aspx.AppendFormat(@"<%@ Page Language=""C#"" AutoEventWireup=""false"" EnableEventValidation=""false"" EnableTheming=""false"" EnableViewState=""false""{0} %>{1}", inherits.Count == 0 ? String.Empty : inherits.Last().BuildAttribute(), Environment.NewLine);

			foreach (var @namespace in namespaces) aspx.AppendFormat(@"<%@ Import Namespace=""{0}"" %>{1}", @namespace, Environment.NewLine);

			aspx.AppendLine();
			aspx.AppendLine("<script runat=\"server\">");
			aspx.AppendLine();
			aspx.AppendLine("	/*******************************************************************************************************************************");
			aspx.AppendLine("	 *");
			aspx.AppendFormat("	 * 应用程序：{0}{1}", this.Application.ProductName, Environment.NewLine);
			aspx.AppendFormat("	 * 当前版本：Version {0}{1}", this.Application.Version, Environment.NewLine);
			aspx.AppendFormat("	 * 版权所有：{0}{1}", this.Application.CopyrightHolder, Environment.NewLine);
			aspx.AppendFormat("	 * 官方网站：{0}{1}", this.Application.HomePage, Environment.NewLine);
			aspx.AppendFormat("	 * 技术论坛：{0}{1}", this.Application.SupportPage, Environment.NewLine);
			aspx.AppendLine("	 *");
			aspx.AppendFormat("	 * 模板文件：~/{0}{1}", this.FileName.Replace(this.ApplicationPath, String.Empty).Replace("\\", "/"), Environment.NewLine);
			aspx.AppendLine("	 * 引用文件：");

			var itemIndex = 0;
			var itemCount = this.TemplateFiles.Count;

			foreach (var item in this.TemplateFiles) aspx.AppendFormat("	 *    {0}~/{1}{2}", (++itemIndex == itemCount) ? '└' : '├', item.Replace(this.ApplicationPath, String.Empty).Replace("\\", "/"), Environment.NewLine);

			aspx.AppendLine("	 *");
			aspx.AppendFormat("	 * 最后更新：{0:yyyy-MM-dd HH:mm:ss}{1}", this.ProcessTime, Environment.NewLine);
			aspx.AppendLine("	 *");
			aspx.AppendLine("	 ******************************************************************************************************************************/");
			aspx.AppendLine();

			// UseStopwatch
			if (this.EnableStopwatch)
			{
				aspx.AppendLine("	public Stopwatch Runningwatch = Stopwatch.StartNew();");
				aspx.AppendLine();
			}

			aspx.AppendLine("	protected override void OnInit(EventArgs e)");
			aspx.AppendLine("	{");

			if (this.EnableRuntimeHandler)
			{
				aspx.AppendLine("		this.BeforeOnInit();");
				aspx.AppendLine();
			}

			aspx.AppendLine("		base.OnInit(e);");
			aspx.AppendLine();

			if (this.EnableRuntimeHandler)
			{
				aspx.AppendLine("		this.AfterOnInit();");
				aspx.AppendLine();
			}

			if (this.EnableSDE)
			{
				aspx.AppendLine("		if (String.IsNullOrEmpty(this.DynamicFileName) || String.IsNullOrEmpty(this.StaticFileName)) throw new HttpException(404, \"请求的资源不在服务器上\");");
				aspx.AppendLine("		if (this.ExecuteSDE()) return;");
				aspx.AppendLine();
				aspx.AppendLine("		var htmlDocument = this._CreateDocument();");
				aspx.AppendLine();
				aspx.AppendLine("		this.UpdateStaticFile(htmlDocument);");
				aspx.AppendLine();
				aspx.AppendLine("		Response.Write(htmlDocument);");
			}
			else
			{
				aspx.AppendLine("		var htmlDocument = this._CreateDocument();");
				aspx.AppendLine();
				aspx.AppendLine("		Response.Write(htmlDocument);");
			}

			aspx.AppendLine("	}");
			aspx.AppendLine();
			aspx.AppendLine("	private StringBuilder _CreateDocument()");
			aspx.AppendLine("	{");

			// 处理代码优先级
			var importantTags = this.Document.Nodes.FindAllNodes<ImportantTag>(item => item.Disabled == false);

			if (importantTags.Count > 0)
			{
				var importantBuilder = new StringBuilder();
				var tags = importantTags.OrderBy(item => item.Priority);

				itemIndex = 0;

				foreach (var tag in tags)
				{
					if (tag.InnerCode.Trim().Length == 0) continue;

					if (itemIndex++ > 0) importantBuilder.AppendLine();

					importantBuilder.AppendFormat("		// 代码段 {0:D4}：{1}", itemIndex, Environment.NewLine);

					var lines = tag.InnerCode.Split(new[] { Environment.NewLine }, StringSplitOptions.None);

					foreach (var line in lines)
					{
						if (String.IsNullOrEmpty(line) || String.IsNullOrEmpty(line.Trim())) continue;

						importantBuilder.AppendLine(TemplateEngineHelper.ConvertToStringFormat(line));
					}
				}

				if (importantBuilder.Length > 0)
				{
					importantBuilder.Insert(0, Environment.NewLine);
					importantBuilder.Insert(0, "		// -------------------------------------------------- 优先执行的代码 开始 --------------------------------------------------");
					importantBuilder.AppendLine("		// -------------------------------------------------- 优先执行的代码 结束 --------------------------------------------------");

					aspx.AppendLine(importantBuilder.ToString());
				}
			}

			aspx.AppendLine("		var document = new StringBuilder();");
			aspx.AppendLine();

			if (this.LanguageEngine != null)
			{
				aspx.AppendLine("		this.ResetLanguage();");
				aspx.AppendLine();
			}

			if (this.EnableRuntimeHandler)
			{
				aspx.AppendLine("		this.BeforeCreateDocument(ref document);");
				aspx.AppendLine();
			}

			// 生成模板代码
			aspx.AppendLine(this.Document.ToAspx());

			if (this.EnableRuntimeHandler)
			{
				aspx.AppendLine("		this.AfterCreateDocument(ref document);");
				aspx.AppendLine();
			}

			// UseStopwatch
			if (this.EnableStopwatch)
			{
				aspx.AppendLine("		this.Runningwatch.Stop();");
				aspx.AppendLine();
				aspx.AppendFormat(@"		document.AppendFormat(""<!-- Published at {0:yyyy-MM-dd HH:mm:ss}, Powered by {1} Version {2} Plus, Execution Time:[{{0}}] -->"", this.Runningwatch.Elapsed);{3}", this.ProcessTime, this.Application.Name, this.Application.Version, Environment.NewLine);
			}
			else aspx.AppendFormat(@"		document.AppendLine(""<!-- Published at {0:yyyy-MM-dd HH:mm:ss}, Powered by {1} Version {2} Plus -->"");{3}", this.ProcessTime, this.Application.Name, this.Application.Version, Environment.NewLine);

			aspx.AppendLine();
			aspx.AppendLine("		return document;");
			aspx.AppendLine("	}");
			aspx.AppendLine();
			aspx.AppendLine("</script>");

			return aspx;
		}

		/// <summary>
		/// 注册自定义模板引擎语句标签。
		/// </summary>
		/// <typeparam name="T">自定义模板引擎语句标签的类型，该类型必须派生于 <see cref="TemplateElement"/>。</typeparam>
		/// <param name="tagName">标签名。</param>
		public void RegisterCustomTag<T>(string tagName) where T : TemplateElement { this._templateTags.Add(tagName, typeof(T)); }

		/// <summary>
		/// 注销自定义模板引擎语句标签。
		/// </summary>
		/// <param name="tagName">标签名。</param>
		public void UnRegisterCustomTag(string tagName) { this._templateTags.Remove(tagName); }

		/// <summary>
		/// 返回当前 <see cref="AspxTemplateEngine"/> 的字符串表示形式。
		/// </summary>
		/// <returns>返回 <see cref="System.String"/>。</returns>
		public override string ToString() { return String.Format(@"FileName = ""{0}""", this.FileName); }
	}
}
